/*
 * acpi.c -- ACPI methods low-level access code for TSM70 class laptops
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * Written by Mathieu Bérard <mathieu.berard@crans.org>, 2006
 *
 */

#include "omnibook.h"
#include "hardware.h"

#ifdef CONFIG_ACPI

#include <acpi/acpi_drivers.h>
#include <linux/workqueue.h>

/* copied from drivers/input/serio/i8042-io.h */
#define I8042_KBD_PHYS_DESC "isa0060/serio0"

/*
 * ACPI backend masks and strings
 */

#define GET_WIRELESS_METHOD "ANTR"
#define SET_WIRELESS_METHOD "ANTW"
#define WLEX_MASK	0x4
#define WLAT_MASK	0x1
#define BTEX_MASK	0x8
#define BTAT_MASK	0x2
#define KLSW_MASK	0x10

#define GET_DISPLAY_METHOD "DOSS"
#define SET_DISPLAY_METHOD "DOSW"
/* Display reading masks CADL = detected, CSTE = enabled */
#define	LCD_CADL	0x10
#define	CRT_CADL	0x20
#define	TVO_CADL	0x40
#define	DVI_CADL	0x80
#define	LCD_CSTE	0x1
#define	CRT_CSTE	0x2
#define	TVO_CSTE	0x4
#define DVI_CSTE	0x8

/* TSX205 Video-Out methods and return values */
#define TSX205_SET_DISPLAY_METHOD "STBL"
#define TSX205_SLI_DISPLAY_METHOD "SL01.VGA1.STBL"
/* NOTE: Method DSSW seems to be some sort of auto-detect method */
#define TSX205_AUTO_DISPLAY_METHOD "DSSW"
#define TSX205_DSPY_DE	0x1F	/* DE - Detected and Enabled */
#define TSX205_DSPY_DN	0x1D	/* DN - Detected and Not enabled */
#define TSX205_DSPY_NE	0x0F	/* NE - Not detected and Enabled */
#define TSX205_DSPY_NN	0x0D	/* NN - Not detected and Not enabled */

#define GET_THROTTLE_METHOD "THRO"
#define	SET_THROTTLE_METHOD "CLCK"

static char ec_dev_list[][20] = {
	"\\_SB.PCI0.LPCB.EC0",
	"\\_SB.PCI0.LPC0.EC0",
};

/* TSX205 HCI and display handles */
static char tsx205_dev_list[][20] = {
	"\\_SB.VALZ",
	"\\_SB.PCI0.PEGP.VGA"
};

/* TSX205 GET video-out methods */
static char tsx205_video_list[][20] = {
	"LCD._DCS",
	"CRT._DCS",
	"TV._DCS",
	"DVI._DCS",
	"SL01.VGA1.LCD._DCS",
	"SL01.VGA1.CRT._DCS",
	"SL01.VGA1.TV._DCS",
	"SL01.VGA1.DVI._DCS",
};

#define TOSHIBA_ACPI_BT_CLASS "bluetooth"
#define TOSHIBA_ACPI_DEVICE_NAME "bluetooth adapter"

#define TOSH_BT_ACTIVATE_USB	"AUSB"
#define TOSH_BT_DISABLE_USB	"DUSB"
#define TOSH_BT_POWER_ON	"BTPO"
#define TOSH_BT_POWER_OFF	"BTPF"
#define TOSH_BT_STATUS		"BTST"
#define	TOSH_BT_KSST_MASK	0x1
#define	TOSH_BT_USB_MASK	0x40
#define	TOSH_BT_POWER_MASK	0x80

/*
 * ACPI driver for Toshiba Bluetooth device
 */
static int omnibook_acpi_bt_add(struct acpi_device *device);
static int omnibook_acpi_bt_remove(struct acpi_device *device, int type);


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23)
static const struct acpi_device_id omnibook_bt_ids[] = {
	{"TOS6205", 0},
	{"", 0},
};

static struct acpi_driver omnibook_bt_driver = {
	.name	= OMNIBOOK_MODULE_NAME,
	.class	= TOSHIBA_ACPI_BT_CLASS,
	.ids	= omnibook_bt_ids,
	.ops	= {
			.add	=  omnibook_acpi_bt_add,
			.remove	=  omnibook_acpi_bt_remove,
		  },
};
#else /* 2.6.23 */
static struct acpi_driver omnibook_bt_driver = {
	.name	= OMNIBOOK_MODULE_NAME,
	.class	= TOSHIBA_ACPI_BT_CLASS,
	.ids	= "TOS6205",
	.ops	= {
			.add	=  omnibook_acpi_bt_add,
			.remove	=  omnibook_acpi_bt_remove,
		  },
};
#endif /* 2.6.23 */


/*
 * ACPI backend private data structure
 */
struct acpi_backend_data {
	acpi_handle ec_handle;  /* Handle on ACPI EC device */
	acpi_handle bt_handle;  /* Handle on ACPI BT device */
	acpi_handle hci_handle; /* Handle on ACPI HCI device */
	acpi_handle dis_handle; /* Handle on ACPI Display device */
	unsigned has_antr_antw:1; /* Are there ANTR/ANTW methods in the EC device ? */
	unsigned has_doss_dosw:1; /* Are there DOSS/DOSW methods in the EC device ? */
	unsigned has_sli:1; /* Does the laptop has SLI enabled ? */
	struct input_dev *acpi_input_dev;
	struct work_struct fnkey_work;
};

/*
 * Hotkeys workflow:
 * 1. Fn+Foo pressed
 * 2. Scancode 0x6e generated by kbd controller
 * 3. Scancode 0x6e caught by omnibook input handler
 * 4. INFO method has keycode of last actually pressed Fn key
 * 5. acpi_scan_table used to associate a detected keycode with a generated one
 * 6. Generated keycode issued using the omnibook input device
 */

/*
 * The input handler should only bind with the standard AT keyboard.
 * XXX: Scancode 0x6e won't be detected if the keyboard has already been
 * grabbed (the Xorg event input driver do that)
 */
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,21))
static int hook_connect(struct input_handler *handler,
					 struct input_dev *dev,
					 const struct input_device_id *id)
#elif (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18))
static struct input_handle *hook_connect(struct input_handler *handler,
					 struct input_dev *dev,
					 const struct input_device_id *id)
#else
static struct input_handle *hook_connect(struct input_handler *handler,
					 struct input_dev *dev,
					 struct input_device_id *id)
#endif
{
	struct input_handle *handle;
	int error;

	/* the 0x0001 vendor magic number is found in atkbd.c */
	if(!(dev->id.bustype == BUS_I8042 && dev->id.vendor == 0x0001))
		goto out_nobind;

	if(!strstr(dev->phys, I8042_KBD_PHYS_DESC))
		goto out_nobind;

	dprintk("hook_connect for device %s.\n", dev->name);

	if(dev->grab)
		printk(O_WARN "Input device is grabbed by %s, Fn hotkeys won't work.\n",
			dev->grab->name);

	handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
	if (!handle)
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,21))
		return -ENOMEM;
#else
		return NULL;
#endif

	handle->dev = dev;
	handle->handler = handler;
	handle->name = "omnibook_scancode_hook";
	handle->private = handler->private;

#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,21))
	error = input_register_handle(handle);
	if (error) {
		dprintk("register_handle failed\n");
		goto out_nobind_free;
	} 
	error = input_open_device(handle);
	if (error) {
		dprintk("register_handle failed\n");
		input_unregister_handle(handle);
		goto out_nobind_free;
	} 
	
#else
	status=input_open_device(handle);
	if (error==0) dprintk("Input device opened\n");
	else { 
		dprintk("opening input device failed\n");
		goto out_nobind_free;
	}
#endif

#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,21))
	return 0;
out_nobind_free:
	kfree(handle);
out_nobind:
	return -ENODEV;
#else
	return handle;
out_nobind_free:
	kfree(handle);
out_nobind:
	return NULL;
#endif	
}

static void hook_disconnect(struct input_handle *handle)
{
	dprintk("hook_disconnect.\n");
	input_close_device(handle);
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,21))
	input_unregister_handle(handle);
#endif
	kfree(handle);
}

/*
 * Hook for scancode 0x6e. Actual handling is done in a workqueue.
 */
static void hook_event(struct input_handle *handle, unsigned int event_type,
		      unsigned int event_code, int value)
{
	if (event_type == EV_MSC && event_code == MSC_SCAN && value == ACPI_FN_SCAN)
		schedule_work(&((struct acpi_backend_data *)handle->private)->fnkey_work);
}

#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18))
static const struct input_device_id hook_ids[] = {
#else
static struct input_device_id hook_ids[] = {
#endif
	{
                .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
                .evbit = { BIT(EV_KEY) },
        },
	{ },    /* Terminating entry */
};

static struct input_handler hook_handler = {
	.event		= hook_event,
	.connect	= hook_connect,
	.disconnect	= hook_disconnect,
	.name		= OMNIBOOK_MODULE_NAME,
	.id_table	= hook_ids,
};

/*
 * Detected scancode to keycode table
 */
static const struct {
	unsigned int scancode;
	unsigned int keycode;
} acpi_scan_table[] = {
	{ HCI_FN_RELEASED,    KEY_FN},
	{ HCI_MUTE,           KEY_MUTE},
	{ HCI_BREAK,          KEY_COFFEE},
	{ HCI_1,              KEY_ZOOMOUT},
	{ HCI_2,              KEY_ZOOMIN},
	{ HCI_SPACE,          KEY_ZOOMRESET},
	{ HCI_BSM,            KEY_BATTERY},
	{ HCI_SUSPEND,        KEY_SLEEP},
	{ HCI_HIBERNATE,      KEY_SUSPEND},
	{ HCI_VIDEOOUT,       KEY_SWITCHVIDEOMODE},
	{ HCI_BRIGHTNESSDOWN, KEY_BRIGHTNESSDOWN},
	{ HCI_BRIGHTNESSUP,   KEY_BRIGHTNESSUP},
	{ HCI_WLAN,           KEY_WLAN},
	{ HCI_TOUCHPAD,       KEY_PROG1},
	{ HCI_FN_PRESSED,     KEY_FN},
	{ 0, 0},
};

#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,19))
static void omnibook_handle_fnkey(struct work_struct *work);
#else
static void omnibook_handle_fnkey(void* data);
#endif

/*
 * Register the input handler and the input device in the input subsystem
 */
static int register_input_subsystem(struct acpi_backend_data *priv_data)
{
	int i, retval = 0;
	struct input_dev *acpi_input_dev;

	acpi_input_dev = input_allocate_device();
	if (!acpi_input_dev) {
		retval = -ENOMEM;
		goto out;
	}

	acpi_input_dev->name = "Omnibook ACPI scancode generator";
	acpi_input_dev->phys = "omnibook/input0";
	acpi_input_dev->id.bustype = BUS_HOST;
	
	set_bit(EV_KEY, acpi_input_dev->evbit);
	
	for(i=0 ; i < ARRAY_SIZE(acpi_scan_table); i++)
		set_bit(acpi_scan_table[i].keycode, acpi_input_dev->keybit);

	retval = input_register_device(acpi_input_dev);
	if (retval) {
		input_free_device(acpi_input_dev);
		goto out;
	}

	priv_data->acpi_input_dev = acpi_input_dev;

#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,19))
	INIT_WORK(&priv_data->fnkey_work, *omnibook_handle_fnkey);
#else
	INIT_WORK(&priv_data->fnkey_work, *omnibook_handle_fnkey, priv_data);
#endif


	hook_handler.private = priv_data;

#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,18))
	retval = input_register_handler(&hook_handler);	
#else
	input_register_handler(&hook_handler);
#endif

	out:	
	return retval;
}

/*
 * Execute an ACPI method which return either an integer or nothing
 * and that require 0 or 1 numerical argument
 * (acpi_evaluate_object wrapper)
 */
static int omnibook_acpi_execute(acpi_handle dev_handle, char *method, const int *param, int *result)
{

	struct acpi_object_list args_list;
	struct acpi_buffer buff;
	union acpi_object arg, out_objs[1];
	
	if (param) {
		args_list.count = 1;
		args_list.pointer = &arg;
		arg.type = ACPI_TYPE_INTEGER;
		arg.integer.value = *param;
	} else
		args_list.count = 0;

	buff.length = sizeof(out_objs);
	buff.pointer = out_objs;

	if (acpi_evaluate_object(dev_handle, method, &args_list, &buff) != AE_OK) {
		printk(O_ERR "ACPI method execution failed\n");
		return -EIO;
	}

	if (!result)		/* We don't care what the method returned here */
		return 0;

	if (out_objs[0].type != ACPI_TYPE_INTEGER) {
		printk(O_ERR "ACPI method result is not a number\n");
		return -EINVAL;
	}

	*result = out_objs[0].integer.value;
	return 0;
}

/*
 * Probe for expected ACPI devices
 */
static int omnibook_acpi_init(const struct omnibook_operation *io_op)
{
	int retval = 0;	
	acpi_handle dev_handle, method_handle, hci_handle, dis_handle;
	int i;
	int has_sli = 0;
	struct acpi_backend_data *priv_data;
	
	if (unlikely(acpi_disabled)) {
		printk(O_ERR "ACPI is disabled: feature unavailable.\n");
		return -ENODEV;
	}

	if (!io_op->backend->data) {
		dprintk("Try to init ACPI backend\n");
		mutex_init(&io_op->backend->mutex);
		mutex_lock(&io_op->backend->mutex);
		kref_init(&io_op->backend->kref);
		priv_data = kzalloc(sizeof(struct acpi_backend_data), GFP_KERNEL);
		if (!priv_data) {
			retval = -ENOMEM;
			goto error0;
		}

		/* Locate ACPI EC device, acpi_get_handle set dev_handle to NULL if not found */
		for (i = 0; i < ARRAY_SIZE(ec_dev_list); i++) {
			if (acpi_get_handle(NULL, ec_dev_list[i], &dev_handle) == AE_OK) {
				dprintk("ACPI EC device found\n");
				priv_data->ec_handle = dev_handle;
				break;
			}
		}
		
		if (!dev_handle) {
			printk(O_ERR "Can't get handle on ACPI EC device.\n");
			retval = -ENODEV;
			goto error1;
		}

		/* Probe for HCI and Display devices only on TSX205 models */
		if (omnibook_ectype & TSX205) {
			if (acpi_get_handle(NULL, tsx205_dev_list[0], &hci_handle) == AE_OK) {
				dprintk("Toshiba X205 HCI device found\n");
				priv_data->hci_handle = hci_handle;
			}

			if (!hci_handle) {
				printk(O_ERR "Couldn't get HCI handle.\n");
				retval = -ENODEV;
				goto error1;
			}

			if (acpi_get_handle(NULL, tsx205_dev_list[1], &dis_handle) == AE_OK)
				priv_data->dis_handle = dis_handle;

			if (!dis_handle) {
				printk(O_ERR "Couldn't get X205 Display handle.\n");
				retval = -ENODEV;
				goto error1;
			}

			/* Does the laptop has SLI enabled? */
			omnibook_acpi_execute(dis_handle, (char *)TSX205_SLIVDO_METHOD, NULL, &has_sli);
			if (has_sli)
				dprintk("Toshiba X205 Display device found (SLI).\n");
			else
				dprintk("Toshiba X205 Display device found.\n");

			priv_data->has_sli = has_sli;
		}

		if ((acpi_get_handle( dev_handle, GET_WIRELESS_METHOD, &method_handle) == AE_OK) &&
		    (acpi_get_handle( dev_handle, SET_WIRELESS_METHOD, &method_handle) == AE_OK))
			priv_data->has_antr_antw = 1;

		if (omnibook_ectype & TSX205) {
			if ((acpi_get_handle(dis_handle, TSX205_AUTO_DISPLAY_METHOD, &method_handle) ==  AE_OK) &&
			    (acpi_get_handle(dis_handle, TSX205_AUTO_DISPLAY_METHOD, &method_handle) ==  AE_OK))
				priv_data->has_doss_dosw = 1;
		} else {
			if ((acpi_get_handle( dev_handle, GET_DISPLAY_METHOD, &method_handle) == AE_OK) &&
			    (acpi_get_handle( dev_handle, SET_DISPLAY_METHOD, &method_handle) == AE_OK))
				priv_data->has_doss_dosw = 1;
		}

		retval = register_input_subsystem(priv_data);
		if(retval)
			goto error1;

		io_op->backend->data = (void *) priv_data;
		
		mutex_unlock(&io_op->backend->mutex);
		
		/* attempt to register Toshiba bluetooth ACPI driver */
		acpi_bus_register_driver(&omnibook_bt_driver);

		dprintk("ACPI backend init OK\n");
		
		return 0;

	} else {
		dprintk("ACPI backend has already been initialized\n");
		kref_get(&io_op->backend->kref);
		return 0;
	}
		
	error1:
	kfree(priv_data);
	io_op->backend->data = NULL;
	error0:
	mutex_unlock(&io_op->backend->mutex);
	mutex_destroy(&io_op->backend->mutex);
	return retval;
}

static void omnibook_acpi_free(struct kref *ref)
{
	struct omnibook_backend *backend;
	struct acpi_backend_data *priv_data;

	backend = container_of(ref, struct omnibook_backend, kref);
	priv_data = backend->data;

	dprintk("ACPI backend not used anymore: disposing\n");

	
	dprintk("ptr addr: %p driver name: %s\n",&omnibook_bt_driver, omnibook_bt_driver.name);
	acpi_bus_unregister_driver(&omnibook_bt_driver);

	flush_scheduled_work();
	input_unregister_handler(&hook_handler);
	input_unregister_device(priv_data->acpi_input_dev);
	
	mutex_lock(&backend->mutex);
	kfree(backend->data);
	backend->data = NULL;
	mutex_unlock(&backend->mutex);
	mutex_destroy(&backend->mutex);
}

static void omnibook_acpi_exit(const struct omnibook_operation *io_op)
{
	dprintk("Trying to dispose ACPI backend\n");
	kref_put(&io_op->backend->kref, omnibook_acpi_free);
}

/* forward declaration */
struct omnibook_backend acpi_backend;

/* Function taken from toshiba_acpi */
static acpi_status hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS])
{
	struct acpi_backend_data *priv_data = acpi_backend.data;
	struct acpi_object_list params;
	union acpi_object in_objs[HCI_WORDS];
	struct acpi_buffer results;
	union acpi_object out_objs[HCI_WORDS + 1];
	acpi_status status;
	int i;

	params.count = HCI_WORDS;
	params.pointer = in_objs;
	for (i = 0; i < HCI_WORDS; ++i) {
		in_objs[i].type = ACPI_TYPE_INTEGER;
		in_objs[i].integer.value = in[i];
	}

	results.length = sizeof(out_objs);
	results.pointer = out_objs;

	status = acpi_evaluate_object(priv_data->hci_handle, (char *)HCI_METHOD, &params,
				      &results);
	if ((status == AE_OK) && (out_objs->package.count <= HCI_WORDS)) {
		for (i = 0; i < out_objs->package.count; ++i) {
			out[i] = out_objs->package.elements[i].integer.value;
		}
	}

	return status;
}

/*
 * Set Bluetooth device state using the Toshiba BT device
 */
static int set_bt_status(const struct acpi_backend_data *priv_data, unsigned int state)
{
	int retval = 0;

	if (state) {
		retval = omnibook_acpi_execute(priv_data->bt_handle, TOSH_BT_ACTIVATE_USB, NULL, NULL);
		if (retval)
			goto out;
		retval = omnibook_acpi_execute(priv_data->bt_handle, TOSH_BT_POWER_ON, NULL, NULL);
		if (retval)
			goto out;
	} else {
		retval = omnibook_acpi_execute(priv_data->bt_handle, TOSH_BT_DISABLE_USB, NULL, NULL);
		if (retval)
			goto out;
		retval = omnibook_acpi_execute(priv_data->bt_handle, TOSH_BT_POWER_OFF, NULL, NULL);
		if (retval)
			goto out;
	}
	out:
	return retval;
}

static int omnibook_acpi_bt_add(struct acpi_device *device)
{
	int retval;
	struct acpi_backend_data *priv_data = acpi_backend.data;
	
	dprintk("Enabling Toshiba Bluetooth ACPI device.\n");
	strcpy(acpi_device_name(device), TOSHIBA_ACPI_DEVICE_NAME);
	strcpy(acpi_device_class(device), TOSHIBA_ACPI_BT_CLASS);

	/* Save handle in backend private data structure. ugly. */

	mutex_lock(&acpi_backend.mutex);
	priv_data->bt_handle = device->handle;
	retval = set_bt_status(priv_data, 1);
	mutex_unlock(&acpi_backend.mutex);

	return retval;
}

static int omnibook_acpi_bt_remove(struct acpi_device *device, int type)
{
	int retval;
	struct acpi_backend_data *priv_data = acpi_backend.data;

	mutex_lock(&acpi_backend.mutex);
	dprintk("Disabling Toshiba Bluetooth ACPI device.\n");
	retval = set_bt_status(priv_data, 0);
	priv_data->bt_handle = NULL;
	mutex_unlock(&acpi_backend.mutex);
	
	return retval;
}

/*
 * Get Bluetooth status using the BTST method
 */
static int get_bt_status(const struct acpi_backend_data *priv_data, unsigned int *state)
{
	int retval = 0;
	int raw_state;

	if ((retval = omnibook_acpi_execute(priv_data->bt_handle, TOSH_BT_STATUS, NULL, &raw_state)))
		return retval;

	dprintk("BTST raw_state: %x\n", raw_state);

	*state = BT_EX;
	*state |= ((raw_state & TOSH_BT_USB_MASK) && (raw_state & TOSH_BT_POWER_MASK)) ? BT_STA : 0;

	return retval;
}

/*
 * Get the Bluetooth + Wireless status using the ANTR method
 * FIXME: what if ANTR and BTST disagree ? we thrust ANTR for now
 */
static int get_wireless_status(const struct acpi_backend_data *priv_data, unsigned int *state)
{
	int retval = 0;
	int raw_state;

	if ((retval = omnibook_acpi_execute(priv_data->ec_handle, GET_WIRELESS_METHOD, NULL, &raw_state)))
		return retval;

	dprintk("get_wireless raw_state: %x\n", raw_state);

	*state = (raw_state & WLEX_MASK) ? WIFI_EX : 0;
	*state |= (raw_state & WLAT_MASK) ? WIFI_STA : 0;
	*state |= (raw_state & KLSW_MASK) ? KILLSWITCH : 0;
	*state |= (raw_state & BTEX_MASK) ? BT_EX : 0;
	*state |= (raw_state & BTAT_MASK) ? BT_STA : 0;

	return retval;
}

static int get_tsx205_wireless_status(const struct acpi_backend_data *priv_data, unsigned int *state)
{
	int retval = 0;
	int raw_state;
	u32 in[HCI_WORDS] = { HCI_GET, HCI_RF_CONTROL, 0, HCI_WIRELESS_CHECK, 0, 0 };
	u32 out[HCI_WORDS];

	hci_raw(in, out);

	/* Now let's check the killswitch */
	if ((retval = omnibook_acpi_execute(priv_data->ec_handle, TSX205_KILLSW_METHOD, NULL, &raw_state)))
		return retval;

	dprintk("get_wireless raw_state: %x\n", out[2]);

	*state = ((out[2] & 0xff)) ? WIFI_EX : 0;
	*state |= (raw_state) ? WIFI_STA : 0;
	*state |= (!raw_state) ? KILLSWITCH : 0;

	/* And finally BT */
	if ((retval = omnibook_acpi_execute(priv_data->bt_handle, TOSH_BT_STATUS, NULL, &raw_state)))
		return retval;
	
	*state |= BT_EX;
	*state |= ((raw_state & TOSH_BT_USB_MASK) && (raw_state & TOSH_BT_POWER_MASK)) ? BT_STA : 0;

	return retval;
}

static int omnibook_acpi_get_wireless(const struct omnibook_operation *io_op, unsigned int *state)
{
	int retval;
	struct acpi_backend_data *priv_data = io_op->backend->data;

	/* use BTST (BT device) if we don't have ANTR/ANTW (EC device) */
	if (omnibook_ectype & TSX205)
		retval = get_tsx205_wireless_status(priv_data, state);
	else if (priv_data->has_antr_antw)
		retval = get_wireless_status(priv_data, state);
	else if(priv_data->bt_handle)
		retval = get_bt_status(priv_data, state);
	else
		retval = -ENODEV;

	return retval;
}

/*
 * Set the Bluetooth + Wireless status using the ANTW method
 */
static int set_wireless_status(const struct acpi_backend_data *priv_data, unsigned int state)
{
	int retval;
	int raw_state;

	raw_state = !!(state & WIFI_STA);	/* bit 0 */
	raw_state |= !!(state & BT_STA) << 0x1;	/* bit 1 */

	dprintk("set_wireless raw_state: %x\n", raw_state);

	retval = omnibook_acpi_execute(priv_data->ec_handle, SET_WIRELESS_METHOD, &raw_state, NULL);

	return retval;
}

static int set_tsx205_wireless_status(const struct acpi_backend_data *priv_data, unsigned int state)
{
	int retval;
	int raw_state = !!(state & WIFI_STA);
	u32 in[HCI_WORDS] = { HCI_SET, HCI_RF_CONTROL, raw_state, HCI_WIRELESS_POWER, 0, 0 };
	u32 out[HCI_WORDS];

	dprintk("set_wireless raw_state: %x\n", raw_state);

	hci_raw(in, out);

	raw_state |= !!(state & BT_STA) << 0x1;	/* bit 1 */

	/* BT status */
	retval = set_bt_status(priv_data->bt_handle, state);

	return retval;
}

static int omnibook_acpi_set_wireless(const struct omnibook_operation *io_op, unsigned int state)
{
	int retval = -ENODEV;
	struct acpi_backend_data *priv_data = io_op->backend->data;

	/* First try the TSX205 methods */
	if(omnibook_ectype & TSX205)
		retval = set_tsx205_wireless_status(priv_data, state);

	/* Then try the ANTR/ANTW methods */
	if(priv_data->has_antr_antw)
		retval = set_wireless_status(priv_data, state);
	
	/* Then try the bluetooth ACPI device if present */
	if(priv_data->bt_handle)
		retval = set_bt_status(priv_data, (state & BT_STA));

	return retval;
}

static int tsx205_get_display(const struct acpi_backend_data *priv_data, unsigned int *state, unsigned int device)
{
	int retval = 0;
	int raw_state = 0;

	retval = omnibook_acpi_execute(priv_data->dis_handle, tsx205_video_list[device], NULL, &raw_state);
	if (retval < 0) {
		dprintk(O_ERR "Failed to get video device (%d) state.\n", device);
		return retval;
	}

	/* Ugly, but better than nothing... */
	switch (device) {
	case 0:
	case 4:	/* LCD device */
		dprintk("get_display LCD (%d) raw_state: %x\n", device, raw_state);
		if (raw_state == TSX205_DSPY_DE) {
			*state |= DISPLAY_LCD_DET;
			*state |= DISPLAY_LCD_ON;
		} else
		if (raw_state == TSX205_DSPY_DN)
			*state |= DISPLAY_LCD_DET;
		else if (raw_state == TSX205_DSPY_NE)
			*state |= DISPLAY_LCD_ON;
		break;
	case 1:
	case 5:	/* CRT device */
		dprintk("get_display CRT (%d) raw_state: %x\n", device, raw_state);
		if (raw_state == TSX205_DSPY_DE) {
			*state |= DISPLAY_CRT_DET;
			*state |= DISPLAY_CRT_ON;
		} else
		if (raw_state == TSX205_DSPY_DN)
			*state |= DISPLAY_CRT_DET;
		else if (raw_state == TSX205_DSPY_NE)
			*state |= DISPLAY_CRT_ON;
		break;
	case 2:
	case 6:	/* TV-OUT device */
		dprintk("get_display TV-OUT (%d) raw_state: %x\n", device, raw_state);
		if (raw_state == TSX205_DSPY_DE) {
			*state |= DISPLAY_TVO_DET;
			*state |= DISPLAY_TVO_ON;
		} else
		if (raw_state == TSX205_DSPY_DN)
			*state |= DISPLAY_TVO_DET;
		else if (raw_state == TSX205_DSPY_NE)
			*state |= DISPLAY_TVO_ON;
		break;
	case 3:
	case 7:	/* DVI device */
		dprintk("get_display DVI (%d) raw_state: %x\n", device, raw_state);
		if (raw_state == TSX205_DSPY_DE) {
			*state |= DISPLAY_DVI_DET;
			*state |= DISPLAY_DVI_ON;
		} else
		if (raw_state == TSX205_DSPY_DN)
			*state |= DISPLAY_DVI_DET;
		else if (raw_state == TSX205_DSPY_NE)
			*state |= DISPLAY_DVI_ON;
		break;
	}

	return retval;
}

static int omnibook_acpi_get_display(const struct omnibook_operation *io_op, unsigned int *state)
{
	int retval = 0;
	int raw_state = 0;
	struct acpi_backend_data *priv_data = io_op->backend->data;
	
	if(!priv_data->has_doss_dosw)
		return -ENODEV;

	if (omnibook_ectype & TSX205) {
		int i;

		/* Loop 'tru the different Video-Out devices */
		if (priv_data->has_sli)
			for (i = 4; i < ARRAY_SIZE(tsx205_video_list); i++)
				retval = tsx205_get_display(priv_data, state, i);
		else
			for (i = 0; i < 4; i++)
				retval = tsx205_get_display(priv_data, state, i);

		if (retval < 0)
			return -EIO;
	
		goto vidout;
	}

	retval = omnibook_acpi_execute(priv_data->ec_handle, GET_DISPLAY_METHOD, NULL, &raw_state);
	if (retval < 0)
		return retval;

	dprintk("get_display raw_state: %x\n", raw_state);

	/* Backend specific to backend-neutral conversion */
	*state = (raw_state & LCD_CSTE) ? DISPLAY_LCD_ON : 0;
	*state |= (raw_state & CRT_CSTE) ? DISPLAY_CRT_ON : 0;
	*state |= (raw_state & TVO_CSTE) ? DISPLAY_TVO_ON : 0;
	*state |= (raw_state & DVI_CSTE) ? DISPLAY_DVI_ON : 0;

	*state |= (raw_state & LCD_CADL) ? DISPLAY_LCD_DET : 0;
	*state |= (raw_state & CRT_CADL) ? DISPLAY_CRT_DET : 0;
	*state |= (raw_state & TVO_CADL) ? DISPLAY_TVO_DET : 0;
	*state |= (raw_state & DVI_CADL) ? DISPLAY_DVI_DET : 0;

vidout:
	return DISPLAY_LCD_ON | DISPLAY_CRT_ON | DISPLAY_TVO_ON | DISPLAY_DVI_ON
	    | DISPLAY_LCD_DET | DISPLAY_CRT_DET | DISPLAY_TVO_DET | DISPLAY_DVI_DET;
}

static const unsigned int acpi_display_mode_list[] = {
	DISPLAY_LCD_ON,
	DISPLAY_CRT_ON,
	DISPLAY_LCD_ON | DISPLAY_CRT_ON,
	DISPLAY_TVO_ON,
	DISPLAY_LCD_ON | DISPLAY_TVO_ON,
	DISPLAY_CRT_ON | DISPLAY_TVO_ON,
	DISPLAY_LCD_ON | DISPLAY_CRT_ON | DISPLAY_TVO_ON,
	DISPLAY_DVI_ON,
	DISPLAY_LCD_ON | DISPLAY_DVI_ON,
};

static int omnibook_acpi_set_display(const struct omnibook_operation *io_op, unsigned int state)
{
	int retval = 0;
	int i; 
	int matched = -1;
	struct acpi_backend_data *priv_data = io_op->backend->data;

	if(!priv_data->has_doss_dosw)
		return -ENODEV;

	for (i = 0; i < ARRAY_SIZE(acpi_display_mode_list); i++) {
		if (acpi_display_mode_list[i] == state) {
			matched = i + 1;	/* raw state is array row number + 1 */
			break;
		}
	}
	if (matched == -1) {
		printk("Display mode %x is unsupported.\n", state);
		return -EINVAL;
	}

	dprintk("set_display raw_state: %x\n", matched);

	if (omnibook_ectype & TSX205) {
		if (priv_data->has_sli)
			retval = omnibook_acpi_execute(priv_data->dis_handle, TSX205_SLI_DISPLAY_METHOD, &matched, NULL);
		else
			retval = omnibook_acpi_execute(priv_data->dis_handle, TSX205_SET_DISPLAY_METHOD, &matched, NULL);
	} else
		retval = omnibook_acpi_execute(priv_data->ec_handle, SET_DISPLAY_METHOD, &matched, NULL);
	if (retval < 0)
		return retval;

	return DISPLAY_LCD_ON | DISPLAY_CRT_ON | DISPLAY_TVO_ON | DISPLAY_DVI_ON;
}

static int omnibook_acpi_get_throttle(const struct omnibook_operation *io_op, unsigned int *state)
{
	int retval;
	int thtl_en = 0, thtl_dty = 0;
	int param;
	struct acpi_backend_data *priv_data = io_op->backend->data;
	
	param = 0;
	/* Read THEN aka THTL_EN in ICH6M datasheets */
	retval = omnibook_acpi_execute(priv_data->ec_handle, GET_THROTTLE_METHOD, &param, &thtl_en); 
	if ( thtl_en == 0 ) {
		*state = 0;
		return retval;
	}
	param = 1;
	/* Read DUTY aka THTL_DTY in ICH6M datasheets */
	retval = omnibook_acpi_execute(priv_data->ec_handle, GET_THROTTLE_METHOD, &param, &thtl_dty);
	WARN_ON(thtl_dty > 7); /* We shouldn't encounter more than 7 throttling level */
	*state = 8 - thtl_dty; /* THTL_DTY and ACPI T-state are reverse mapped */
	return retval;
}

static int omnibook_acpi_set_throttle(const struct omnibook_operation *io_op, unsigned int state)
{
	struct acpi_backend_data *priv_data = io_op->backend->data;
	/* THTL_DTY and ACPI T-state are reverse mapped */
	/* throttling.c already clamped state between 0 and 7 */
	if (state) 
		state = 8 - state;

	return omnibook_acpi_execute(priv_data->ec_handle, SET_THROTTLE_METHOD, &state, NULL);
}

/*
 * Fn+foo hotkeys handling
 */
static int omnibook_hci_get_hotkeys(const struct omnibook_operation *io_op, unsigned int *state)
{
	u32 in[HCI_WORDS] = { HCI_GET, HCI_HOTKEY_EVENT, 0, 0, 0, 0 };
	u32 out[HCI_WORDS];
	acpi_status status = hci_raw(in, out);

	if (status != AE_OK)
		return HCI_FAILURE;

	dprintk("get_hotkeys raw_state: %x\n", out[2]);

	*state = (out[2] & ACPI_FN_MASK) ? HKEY_FN : 0;

	return 0;
}

static int omnibook_hci_set_hotkeys(const struct omnibook_operation *io_op, unsigned int state)
{
	u32 in[HCI_WORDS] = { 0, 0, 0, 0, 0, 0 };
	u32 out[HCI_WORDS];
	acpi_status status;
	in[0] = HCI_SET;
	in[1] = HCI_HOTKEY_EVENT;
	in[2] = (state & HKEY_FN) ? 1 : 0;

	status = hci_raw(in, out);

	dprintk("set_hotkeys (Fn interface) raw_state: %x\n", in[2]);

	return (status == AE_OK) ? out[0] : HCI_FAILURE;
}

static int omnibook_acpi_get_events(unsigned int *state)
{
	acpi_status status;
	struct acpi_backend_data *priv_data = acpi_backend.data;
  
	/* We need to call the NTFY method first so it can activate the TECF variable */
	status = omnibook_acpi_execute(priv_data->ec_handle, TSX205_NOTIFY_METHOD, NULL, NULL);
	if (status != AE_OK) {
		dprintk(O_ERR "Failed to activate NTFY method.\n");
		return -EIO;
	}

	/* Now we can poll the INFO method to get last pressed hotkey */
	status = omnibook_acpi_execute(priv_data->hci_handle, TSX205_EVENTS_METHOD, NULL, state);
	if (status != AE_OK) {
		dprintk(O_ERR "Failed to get Hotkey event.\n");
		return -EIO;
	}

	/* We only care about a key press, so just report the Fn key Press/Release */
	if ( ((*state & ~0x80) == 0x100) || ((*state & ~0x80) == 0x17f) )
		*state &= ~0x80;

	return status;
}

/*
 * Adjust the lcd backlight level by delta.
 * Used for Fn+F6/F7 keypress
 */
static int adjust_brighness(int delta)
{
	struct omnibook_feature *lcd_feature = omnibook_find_feature("lcd");
	struct omnibook_operation *io_op;
	int retval = 0;
	u8 brgt;

	if(!lcd_feature)
		return -ENODEV;

	io_op = lcd_feature->io_op;

	mutex_lock(&io_op->backend->mutex);

	if(( retval = __backend_byte_read(io_op, &brgt)))
		goto out;	

	dprintk("Fn-F6/F7 pressed: adjusting brightness.\n");

	if (((int) brgt + delta) < 0)
		brgt = 0;
	else if ((brgt + delta) > omnibook_max_brightness)
		brgt = omnibook_max_brightness;
	else
		brgt += delta;

	retval = __backend_byte_write(io_op, brgt);

	out:
	mutex_unlock(&io_op->backend->mutex);
	return retval;
}

/*
 * Workqueue handler for Fn hotkeys
 */
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,19))
static void omnibook_handle_fnkey(struct work_struct *work)
#else
static void omnibook_handle_fnkey(void* data)
#endif
{
	int i;
	u32 gen_scan;
	struct input_dev *input_dev;
	acpi_status status;

	status = omnibook_acpi_get_events(&gen_scan);
	if (status != AE_OK)
		return;

	dprintk("detected scancode 0x%x.\n", gen_scan);
	switch(gen_scan) {
	case HCI_BRIGHTNESSDOWN:
		adjust_brighness(-1);
		break;
	case HCI_BRIGHTNESSUP:
		adjust_brighness(+1);
		break;
	}

	for (i = 0 ; i < ARRAY_SIZE(acpi_scan_table); i++) {
		if (gen_scan == acpi_scan_table[i].scancode) {
			dprintk("generating keycode %i.\n", acpi_scan_table[i].keycode);
#if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,19))
			input_dev = container_of(work, struct acpi_backend_data, fnkey_work)->acpi_input_dev;
#else
			input_dev = ((struct acpi_backend_data *) data)->acpi_input_dev;
#endif
			omnibook_report_key(input_dev, acpi_scan_table[i].keycode);
			break;
		}
	}
}

struct omnibook_backend acpi_backend = {
	.name = "acpi",
	.hotkeys_read_cap = HKEY_FN,
	.hotkeys_write_cap = HKEY_FN,
	.init = omnibook_acpi_init,
	.exit = omnibook_acpi_exit,
	.aerial_get = omnibook_acpi_get_wireless,
	.aerial_set = omnibook_acpi_set_wireless,
	.display_get = omnibook_acpi_get_display,
	.display_set = omnibook_acpi_set_display,
	.throttle_get = omnibook_acpi_get_throttle,
	.throttle_set = omnibook_acpi_set_throttle,
	.hotkeys_get = omnibook_hci_get_hotkeys,
	.hotkeys_set = omnibook_hci_set_hotkeys,
};

#else				/* CONFIG_ACPI */

/* dummy backend for non-ACPI systems */
static int _fail_probe(const struct omnibook_operation *io_op)
{
	return -ENODEV;
}

struct omnibook_backend acpi_backend = {
	.name = "acpi",
	.init = _fail_probe,
};

#endif				/* CONFIG_ACPI */
